package com.example.mydemoproject.config.mybatis;

import com.example.mydemoproject.model.common.LoginUser;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import org.apache.ibatis.binding.MapperMethod;
import org.apache.ibatis.executor.Executor;
import org.apache.ibatis.mapping.MappedStatement;
import org.apache.ibatis.mapping.SqlCommandType;
import org.apache.ibatis.plugin.*;
import org.springframework.stereotype.Component;
import org.springframework.util.ReflectionUtils;

import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.time.LocalDateTime;
import java.util.*;

/**
 * @author LiuZhiYong
 * @description MyBatis拦截器
 * @date 2022-10-31
 **/
@Slf4j
@Component
@Intercepts({@Signature(type = Executor.class, method = "update", args = {MappedStatement.class, Object.class})})
public class MybatisInterceptor implements Interceptor {

    /**
     * Date类型 用于判断careteTime和updateTime的类型
     */
    private static final String TYPE_DATE = "class java.util.Date";

    /**
     * LocalDateTime类型 用于判断careteTime和updateTime的类型
     */
    private static final String TYPE_LOCAL_DATE_TIME = "class java.time.LocalDateTime";
    private static final String ET = "et";
    private static final String INSERT_FLAG = "INSERT";
    private static final String UPDATE_FLAG = "UPDATE";
    private static final String CREATE_BY = "createBy";
    private static final String CREATE_TIME = "createTime";
    private static final String UPDATE_BY = "updateBy";
    private static final String UPDATE_TIME = "updateTime";


    /**
     * 代理对象每次调用的方法，就是要进行拦截的时候要执行的方法。在这个方法里面做自定义的逻辑处理
     * invocation.getArgs()[0] MappedStatement通道
     * invocation.getArgs()[1] argument参数
     *
     * @return java.lang.Object
     * @author liuzhiyong
     * @date 2022/10/31
     */
    @Override
    public Object intercept(Invocation invocation) throws Throwable {
        MappedStatement mappedStatement = (MappedStatement) invocation.getArgs()[0];
        SqlCommandType sqlCommandType = mappedStatement.getSqlCommandType();
        Object parameter = invocation.getArgs()[1];

        //如果没有参数, 则直接放行执行代理无需拦截
        if (parameter == null) {
            return invocation.proceed();
        }
        if (SqlCommandType.INSERT == sqlCommandType) {
            return handleInsert(invocation, parameter);
        }
        if (SqlCommandType.UPDATE == sqlCommandType) {
            return handleUpdate(invocation, parameter);
        }
        return invocation.proceed();
    }

    /**
     * plugin方法是拦截器用于封装目标对象的，通过该方法我们可以返回目标对象本身，也可以返回一个它的代理
     *
     * 当返回的是代理的时候我们可以对其中的方法进行拦截来调用intercept方法 -- Plugin.wrap(target, this)
     * 当返回的是当前对象的时候 就不会调用intercept方法，相当于当前拦截器无效
     *
     * @return java.lang.Object
     * @author liuzhiyong
     * @date 2022/10/31
     */
    @Override
    public Object plugin(Object target) {
        return Plugin.wrap(target, this);
    }

    /**
     * 用于在Mybatis配置文件中指定一些属性的，注册当前拦截器的时候可以设置一些属性
     *
     * @param properties 属性
     * @author liuzhiyong
     * @date 2022/10/31
     */
    @Override
    public void setProperties(Properties properties) {
        // do nothing
    }

    /**
     * 获取当前登录人信息
     *
     * @return com.dajia.mss.coupon.security.UserCache
     * @author liuzhiyong
     * @date 2022/11/2
     */
    private LoginUser getLoginUser() {
        LoginUser userCache = null;
        try {
            userCache = new LoginUser();
            userCache.setUsername("admin");
        } catch (Exception e) {
            // 获取当前登录人信息
            // 用于填充createBy和updateBy 如果出异常 捕获 未处理 不影响程序的继续执行
        }
        return userCache;
    }

    /**
     * 获取类的所有属性，包括父类
     *
     * @param object 对象
     * @return java.lang.reflect.Field[]
     * @author liuzhiyong
     * @date 2022/11/1
     */
    private Field[] getAllFields(Object object) {
        Class<?> clazz = object.getClass();
        List<Field> fieldList = new ArrayList<>();
        while (clazz != null) {
            fieldList.addAll(new ArrayList<>(Arrays.asList(clazz.getDeclaredFields())));
            clazz = clazz.getSuperclass();
        }
        Field[] fields = new Field[fieldList.size()];
        fieldList.toArray(fields);
        return fields;
    }

    /**
     * 处理insert操作 填充属性
     *
     * @return java.lang.Object
     * @author liuzhiyong
     * @date 2022/11/2
     */
    private Object handleInsert(Invocation invocation, Object parameter) throws InvocationTargetException, IllegalAccessException {
        Field[] fieldArray = this.getAllFields(parameter);
        for (Field field : fieldArray) {
            fillProperties(INSERT_FLAG, field, parameter);
        }
        return invocation.proceed();
    }

    /**
     * 处理update操作
     *
     * @return java.lang.Object
     * @author liuzhiyong
     * @date 2022/11/2
     */
    private Object handleUpdate(Invocation invocation, Object parameter) throws InvocationTargetException, IllegalAccessException {
        Field[] fields;
        if (parameter instanceof MapperMethod.ParamMap) {
            MapperMethod.ParamMap<?> p = (MapperMethod.ParamMap<?>) parameter;
            if (p.containsKey(ET)) {
                parameter = p.get(ET);
            } else {
                parameter = p.get("param1");
            }

            if (parameter == null) {
                return invocation.proceed();
            }
            fields = this.getAllFields(parameter);
        } else {
            fields = this.getAllFields(parameter);
        }
        for (Field field : fields) {
            fillProperties(UPDATE_FLAG, field, parameter);
        }
        return invocation.proceed();
    }

    private void fillProperties(String option, Field field, Object parameter) {
        try {
            //给createBy设置登录人信息
            if (INSERT_FLAG.equals(option) && CREATE_BY.equals(field.getName())) {
                LoginUser loginUser = this.getLoginUser();
                handleCreateByOrUpdateBy(field, parameter, loginUser);
            }
            // 注入创建时间
            if (INSERT_FLAG.equals(option) && CREATE_TIME.equals(field.getName())) {
                handleCreateTimeOrUpdateTime(field, parameter);
            }
            // 给updateBy设置登录人信息
            if (UPDATE_FLAG.equals(option) && UPDATE_BY.equals(field.getName())) {
                LoginUser loginUser = this.getLoginUser();
                handleCreateByOrUpdateBy(field, parameter, loginUser);
            }
            // 给updateTime填充更新时间
            if (UPDATE_FLAG.equals(option) && UPDATE_TIME.equals(field.getName())) {
                handleCreateTimeOrUpdateTime(field, parameter);
            }
        } catch (Exception e){
            // 不影响后续流程, 不处理异常
        }
    }


    /**
     * 处理createBy或者updateBy 设置登录人工号
     *
     * @param field     字段对象
     * @param parameter 参数对象
     * @param loginUser   用户缓存
     * @author liuzhiyong
     * @date 2022/11/2
     */
    private void handleCreateByOrUpdateBy(Field field, Object parameter, LoginUser loginUser) throws IllegalAccessException {
        ReflectionUtils.makeAccessible(field);
        Object localCreateByOrUpdateBy = field.get(parameter);
        field.setAccessible(false);
        boolean flag = (localCreateByOrUpdateBy == null || "".equals(localCreateByOrUpdateBy))
                && (loginUser != null && StringUtils.isNotBlank(loginUser.getUsername()));
        if (flag) {
            // 设置登录人工号
            ReflectionUtils.makeAccessible(field);
            ReflectionUtils.setField(field, parameter, loginUser.getUsername());
            field.setAccessible(false);
        }
    }

    /**
     * 处理createTime或者updateTime 填充为当前时间
     *
     * @param field     字段对象
     * @param parameter 参数对象
     * @author liuzhiyong
     * @date 2022/11/2
     */
    private void handleCreateTimeOrUpdateTime(Field field, Object parameter) throws IllegalAccessException {
        ReflectionUtils.makeAccessible(field);
        Object localCreateDate = field.get(parameter);
        field.setAccessible(false);
        if (localCreateDate == null || "".equals(localCreateDate)) {
            ReflectionUtils.makeAccessible(field);
            String type = field.getGenericType().toString();
            if (TYPE_DATE.equals(type)) {
                ReflectionUtils.setField(field, parameter, new Date());
            } else if (TYPE_LOCAL_DATE_TIME.equals(type)) {
                ReflectionUtils.setField(field, parameter, LocalDateTime.now());
            }
            field.setAccessible(false);
        }
    }

}
